#include "common.h"
#include "FreeRTOS.h"
#include "task.h"
#include "stream_buffer.h"
#include "semphr.h"
#include "em_usart.h"
#include "em_cmu.h"
#include "em_ldma.h"
#include "lwrb.h"

#include "bsp_usart.h"

TaskHandle_t usart1_task_handle;
#define StreamBufferTotalBytes              128
#define StreamBufferTriggerLevelBytes       5
static SemaphoreHandle_t usart1_mutex;

static struct dma_recv_instance usart1_recv;
static struct dma_send_instance usart1_send;

/* Ping-Pong buffer size and constant for Ping-Pong transfer */
#define PP_BUFFER_SIZE      64

/* Descriptor linked list for LDMA transfer */
static LDMA_Descriptor_t descLink[2];

/* Buffer for Ping-Pong transfer */

static struct ldmaBuffer_t
{
    uint8_t pingBuffer[PP_BUFFER_SIZE];
    uint8_t pongBuffer[PP_BUFFER_SIZE];
}ldmaBuffer;

static uint8_t usart1_buf[PP_BUFFER_SIZE * 2];
/**************************************************************************
 * @brief Initialize USART1
 * USART1 USART1 中断发送
 **************************************************************************/
static void bsp_USART1Init (void)
{
     /* Configure GPIO pins */
    CMU_ClockEnable(cmuClock_GPIO, true);
    CMU_ClockEnable(cmuClock_HFPER, true);
    CMU_ClockEnable(cmuClock_USART1, true);
    /* To avoid false start, configure output as high */
    GPIO_PinModeSet(USART1_TX_PORT, USART1_TX_PIN, gpioModePushPull, 1);
    GPIO_PinModeSet(USART1_RX_PORT, USART1_RX_PIN, gpioModeInputPull, 1);
    USART_InitAsync_TypeDef config = USART_INITASYNC_DEFAULT; //异步模式
    // Initialize USART asynchronous mode and route pins
    config.baudrate = USART1_BAUDRATE;// 1Mbps
    USART_InitAsync(USART1, &config);
    USART1->ROUTELOC0 = USART1_ROUTELOC;
    USART1->ROUTEPEN = USART1_ROUTEPEN;
#if USART1_RX_IRQ
    NVIC_EnableIRQ(USART1_RX_IRQn);
    //USART_IntDisable(USART1, USART_IEN_RXDATAV);
    USART_IntEnable(USART1, USART_IEN_RXDATAV);
#endif
#if USART1_TX_IRQ
    USART_IntEnable(USART1, USART_IEN_TXC);
    NVIC_EnableIRQ(USART1_TX_IRQn);
#endif
    USART_IntClear(USART1, USART_IntGet(USART1));
}


/**************************************************************************//**
 * @brief USART1 TX IRQ Handler
 *****************************************************************************/
void USART1_TX_IRQHandler(void)
{
    uint32_t flags;
    flags = USART_IntGet(USART1);
    USART_IntClear(USART1, flags);
    if(flags & USART_IF_TXC)
    {
        usart1_send.transmit_status = IDLE;
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        vTaskNotifyGiveFromISR(usart1_task_handle, &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
}

void USART1_RX_IRQHandler(void)
{
    uint32_t flags;
    flags = USART_IntGet(USART1);
    USART_IntClear(USART1, flags);
    if(flags & USART_IF_RXDATAV)
    {
    }
}


/* Use peripheral transfer configuration macro */
static LDMA_TransferCfg_t periTransferRx = (LDMA_TransferCfg_t)LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART1_RXDATAV);

static void initRecipientLdma(void)
{
    /* LINK descriptor macros for Ping-Pong transfer */
    descLink[0] = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_LINKREL_P2M_BYTE(&(USART1->RXDATA), &ldmaBuffer.pingBuffer, PP_BUFFER_SIZE, 1);
    descLink[1] = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_LINKREL_P2M_BYTE(&(USART1->RXDATA), &ldmaBuffer.pongBuffer, PP_BUFFER_SIZE, -1);

    /* Enable interrupts */
    descLink[0].xfer.doneIfs = false;
    descLink[1].xfer.doneIfs = false;

    /* Disable automatic transfers */
    descLink[0].xfer.structReq = false;
    descLink[1].xfer.structReq = false;

    LDMA_StartTransfer(LDMA_USART1_RX_CHANNEL, (void*)&periTransferRx, (void*)&descLink);

    /* Software request to start transfer */
    LDMA->SWREQ |= LDMA_USART1_RX_CH_MASK;
}

/* Use peripheral transfer configuration macro */
static LDMA_TransferCfg_t periTransferTx = (LDMA_TransferCfg_t)LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART1_TXBL);
static LDMA_Descriptor_t descSenderLink;

static void initSenderLdma(void)
{
    descSenderLink = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_SINGLE_M2P_BYTE(NULL, &(USART1->TXDATA), 1);
    descSenderLink.xfer.doneIfs = false;
    /* Disable automatic transfers */
    descSenderLink.xfer.structReq = false;
}

static void SenderLdma(uint8_t *src, uint16_t lens)
{
    descSenderLink.xfer.srcAddr = (uint32_t) src;
    descSenderLink.xfer.xferCnt = lens - 1;
    /* Software request to start transfer */
    LDMA_StartTransfer(LDMA_USART1_TX_CHANNEL, (void*)&periTransferTx, (void*)&descSenderLink);
    // LDMA->SWREQ |= LDMA_USART1_TX_CH_MASK;
}

static void usart1_init(void)
{
    bsp_USART1Init();
    dma_usart_recv_instance_init(&usart1_recv, 
                                                        usart1_buf, 
                                                        PP_BUFFER_SIZE * 2, 
                                                        ldmaBuffer.pingBuffer, 
                                                        LDMA_USART1_RX_CHANNEL);
    dma_usart_send_instance_init(&usart1_send,
                                                StreamBufferTotalBytes, 
                                                StreamBufferTriggerLevelBytes, 
                                                LDMA_USART1_TX_CHANNEL);
    initRecipientLdma();
    initSenderLdma();

}

static int usart1_write(elab_device_t *me, const char * buff, size_t lens)
{
  (void) me;
    xSemaphoreTake(usart1_mutex, portMAX_DELAY);
    int ret, j;
    for (j = 0; j < lens ; j ++ )
    {
        USART_Tx(USART1, buff[j]);
    }
    ret = lens;
    xSemaphoreGive(usart1_mutex);
    return ret;
}

static int w[2] = {0};
static int usart1_write_noblock(elab_device_t *me, const char * buff, size_t lens)
{
  (void) me;
    w[0] += lens;
    xSemaphoreTake(usart1_mutex, portMAX_DELAY);

    int ret = 0;
    int total = 0;
    int try = 10;
    do
    {
        ret = xStreamBufferSend(usart1_send.stream, buff + total, lens, 0);
        if(!ret)
        {
            int left = xStreamBufferSpacesAvailable(usart1_send.stream);
            if(left > 0)
            {
                ret = xStreamBufferSend(usart1_send.stream, 
                                                                buff + total, 
                                                                lens, 
                                                                0);
            }
        }
        lens -= ret;
        total += ret;
        if(lens <= 0)
        {
          break;
        }
        vTaskDelay(10);
    }
    while(--try);
    if(lens > 0)
      {
        lens = lens;
        try = 1;
      }
    xSemaphoreGive(usart1_mutex);
    return 0;
}


static int usart1_read(elab_device_t *me, char * buff, size_t lens)
{
  (void) me;
    int ret = lwrb_read(&usart1_recv.rb, buff, lens);
    return ret;
}

static int usart1_select(elab_device_t *me, uint32_t attr)
{
  (void) me;
  (void) attr;
    int ret = dmaMsgStore(&usart1_recv);
    return ret;
}

elab_device_t usart1 = {
    .attr = {.name = "third_device"},
    .user_data = NULL,
};

static const struct file_operations ops = {
    .write = usart1_write_noblock,
    .read = usart1_read,
    .open = NULL,
    .release = NULL,
    .select = usart1_select,
};


static uint8_t usart1_tx[64];
static uint8_t usart1_tx_cache[64];
static void usart1_tx_task(void *param)
{
    (void) param;
    size_t xReceivedBytes;
    while(1)
    {
        xReceivedBytes =  xStreamBufferReceive( usart1_send.stream, usart1_tx, sizeof(usart1_tx), 100);
        
        if(xReceivedBytes > 0)
        {
            if(usart1_send.transmit_status == BUSY)
            {
                ulTaskNotifyTake( pdTRUE, /* Clear the notification value on exit. */
                                      20 );/* Block indefinitely. */
            }
            if(usart1_send.transmit_status == IDLE)
            {
                ulTaskNotifyTake( pdTRUE, /* Clear the notification value on exit. */
                                        0);/* Block indefinitely. */
                memcpy(usart1_tx_cache, usart1_tx, xReceivedBytes);
                w[1] += xReceivedBytes;
                usart1_send.transmit_status = BUSY;
                SenderLdma(usart1_tx_cache, xReceivedBytes);
            }
        }
    }
}

static void usart1_register(void)
{
    usart1_init();
    usart1_mutex = xSemaphoreCreateMutex();
    if (xTaskCreate(usart1_tx_task, "usart1_tx", 128, NULL, 2, &usart1_task_handle) != pdPASS)
    {
        elog_error("shell task creat failed");
    }
    usart1.ops = &ops;
    device_register(&usart1);
}

INIT_EXPORT(usart1_register, EXPORT_DRVIVER);

